﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Anagramme.Model;
using System.Windows.Input;
using System.ApplicationModel;
using Anagramme.Views;
using System.ComponentModel.Composition;
using Anagramme.Contracts;
using System.ComponentModel.DataAnnotations;
using Anagramme.Contracts.Resources;
using System.ApplicationModel.Composition;
using System.ComponentModel;
using System.ComponentModel.Composition.Hosting;
using System.Windows;
using System.Collections.ObjectModel;
using System.Windows.Data;
using System.Net;
using System.Net.Sockets;
using Anagramme.Services;
using System.Activities;
using Anagramme.Activities.BusinessRules;

namespace Anagramme.ViewModels
{
    /// <summary>
    /// ViewModel du lobby
    /// </summary>
    [ViewModelExport(Modules.Lobby)]
    [PartCreationPolicy(System.ComponentModel.Composition.CreationPolicy.NonShared)]
    public class LobbyViewModel : ViewModelBase<ILobbyModel>
    {
        #region Constructors
        /// <summary>
        /// Crée une nouvelle instance de la classe <see cref="LobbyViewModel"/>
        /// </summary>
        public LobbyViewModel()
        {
            ILobbyModel model = this.Model;
            WeakEventManager<ILobbyModel, EventArgs>.AddHandler(model, "OnResponseLobbyDisconnect", this.Model_OnResponseLobbyDisconnect);
            WeakEventManager<ILobbyModel, EventArgs<GameModes>>.AddHandler(model, "OnResponseLobbyChangeGameMode", this.Model_OnResponseLobbyGameModeChanged);
            WeakEventManager<ILobbyModel, EventArgs<int>>.AddHandler(model, "OnResponseLobbyChangeMaxWordsLength", this.Model_OnResponseLobbyMaxWordsLengthChanged);
            WeakEventManager<ILobbyModel, EventArgs<int>>.AddHandler(model, "OnResponseLobbyChangeMinWordsLength", this.Model_OnResponseLobbyMinWordsLengthChanged);
            WeakEventManager<ILobbyModel, EventArgs<int>>.AddHandler(model, "OnResponseLobbyChangeTargetRounds", this.Model_OnResponseLobbyTargetRoundsChanged);
            WeakEventManager<ILobbyModel, EventArgs<int>>.AddHandler(model, "OnResponseLobbyChangeTargetScore", this.Model_OnResponseLobbyTargetScoreChanged);
            WeakEventManager<ILobbyModel, EventArgs>.AddHandler(model, "OnResponseLobbyLoadGame", this.Model_OnResponseLobbyLoadGame);
            WeakEventManager<ILobbyModel, EventArgs<int, bool>>.AddHandler(model, "OnResponseLobbyPlayerIsReadyToPlay", this.Model_OnResponseLobbyPlayerIsReadyChanged);
            WeakEventManager<ILobbyModel, EventArgs<Player>>.AddHandler(model, "OnResponseLobbyPlayerJoin", this.Model_OnResponseLobbyPlayerJoined);
            WeakEventManager<ILobbyModel, EventArgs<int>>.AddHandler(model, "OnResponseLobbyPlayerLeave", this.Model_OnResponseLobbyPlayerLeaved);

            base.BeginWaitingAction();
            WeakEventManager<ILobbyModel, EventArgs<IEnumerable<Player>>>.AddHandler(model, "OnResponseLobbyPlayers", this.Model_OnResponseLobbyPlayers);
            model.RequestLobbyPlayers();

            base.BeginWaitingAction();
            WeakEventManager<ILobbyModel, EventArgs<LobbyInfo>>.AddHandler(model, "OnResponseLobbyInfos", this.Model_OnResponseLobbyInfos);
            model.RequestLobbyInfos();
        }
        #endregion

        #region Fields
        private bool _isReady;
        private string _serverIP;
        private LobbyInfo _infos;
        private ObservableCollection<Player> _players;

        private ICommand _readyToPlayCommand;
        private ICommand _goBackCommand;
        private ICommand _startCommand;
        #endregion

        #region Public Properties
        /// <summary>
        /// Obtient la liste des joueurs
        /// </summary>
        public ObservableCollection<Player> Players
        {
            get { return _players; }
        }

        /// <summary>
        /// Obtient les informations sur le lobby
        /// </summary>
        public LobbyInfo Infos
        {
            get { return this._infos; }
            private set
            {
                if (!object.ReferenceEquals(this._infos, value))
                {
                    base.RaisePropertyChanging();
                    this._infos = value;
                    base.TraceAssign(value);
                    base.RaisePropertyChanged();
                }
            }
        }

        /// <summary>
        /// Obtient ou défini une valeur indiquant si le joueur est prêt à démarrer la partie
        /// </summary>
        public bool IsReady
        {
            get { return this._isReady; }
            set
            {
                if (this._isReady != value)
                {
                    base.RaisePropertyChanging();
                    this._isReady = value;
                    base.TraceAssign(value);
                    base.RaisePropertyChanged();
                }
            }
        }

        /// <summary>
        /// Obtient les IP LAN et Internet de la machine
        /// </summary>
        /// <remarks>Le format renvoyé est 'IP LAN | IP Internet'</remarks>
        public string ServerIP
        {
            get { return this._serverIP; }
            private set
            {
                if (this._serverIP != value)
                {
                    base.RaisePropertyChanging();
                    this._serverIP = value;
                    base.TraceAssign(value);
                    base.RaisePropertyChanged();
                }
            }
        }
        #endregion

        #region Commands
        /// <summary>
        /// Obtient la commande pour modifier le statut "Prêt" du joueur
        /// </summary>
        public ICommand ReadyToPlayCommand
        {
            get
            {
                if (object.ReferenceEquals(this._readyToPlayCommand, null))
                {
                    this._readyToPlayCommand = new DelegateCommand(this.ReadyToPlay, this.CanReadyToPlay);
                }
                return this._readyToPlayCommand;
            }
        }

        /// <summary>
        /// Obtient la commande pour retourner à la page d'accueil
        /// </summary>
        public ICommand GoBackCommand
        {
            get
            {
                if (object.ReferenceEquals(this._goBackCommand, null))
                {
                    this._goBackCommand = new DelegateCommand(this.GoBack, this.CanGoBack);
                }
                return this._goBackCommand;
            }
        }

        /// <summary>
        /// Obtient la commande pour démarrer la partie
        /// </summary>
        public ICommand StartCommand
        {
            get
            {
                if (object.ReferenceEquals(this._startCommand, null))
                {
                    this._startCommand = new DelegateCommand(this.Start, this.CanStart);
                }
                return this._startCommand;
            }
        }
        #endregion

        #region Model Callbacks
        private void Model_OnResponseLobbyDisconnect(object sender, EventArgs e)
        {
            ApplicationServices.Trace.TraceMethodEntering();

            if (!base.Player.IsLobbyMaster)
            {
                ApplicationServices.Message.ShowInformation(Resources.LobbyStrings.CONNECTION_LOST);
            }
            ApplicationServices.ViewWorkflow.SendMessage(Messages.LobbyGoback);

            ApplicationServices.Trace.TraceMethodLeaving();
        }

        private void Model_OnResponseLobbyGameModeChanged(object sender, EventArgs<GameModes> e)
        {
            ApplicationServices.Trace.TraceMethodEntering();

            this.Infos.GameMode = e.Value;

            ApplicationServices.Trace.TraceMethodLeaving();
        }

        private void Model_OnResponseLobbyInfos(object sender, EventArgs<LobbyInfo> e)
        {
            ApplicationServices.Trace.TraceMethodEntering();

            WeakEventManager<ILobbyModel, EventArgs<LobbyInfo>>.RemoveHandler(base.Model, "OnResponseLobbyInfos", this.Model_OnResponseLobbyInfos);
            this.Infos = e.Value;
            if (this.Player.IsLobbyMaster)
            {
                this.ServerIP = GetLocalIP();
                this.GetInternetIPAsync();
            }
            base.EndWaitingAction();

            ApplicationServices.Trace.TraceMethodLeaving();
        }

        private void Model_OnResponseLobbyLoadGame(object sender, EventArgs e)
        {
            ApplicationServices.Trace.TraceMethodEntering();

            ApplicationServices.ViewWorkflow.SendMessage(Messages.LobbyStart);

            ApplicationServices.Trace.TraceMethodLeaving();
        }

        private void Model_OnResponseLobbyMaxWordsLengthChanged(object sender, EventArgs<int> e)
        {
            ApplicationServices.Trace.TraceMethodEntering();

            this.Infos.MaxWordsLength = e.Value;

            ApplicationServices.Trace.TraceMethodLeaving();
        }

        private void Model_OnResponseLobbyMinWordsLengthChanged(object sender, EventArgs<int> e)
        {
            ApplicationServices.Trace.TraceMethodEntering();

            this.Infos.MinWordsLength = e.Value;

            ApplicationServices.Trace.TraceMethodLeaving();
        }

        private void Model_OnResponseLobbyPlayerIsReadyChanged(object sender, EventArgs<int, bool> e)
        {
            ApplicationServices.Trace.TraceMethodEntering();

            Player player = this._players.SingleOrDefault(p => p.Id.Equals(e.Value));
            if (!object.ReferenceEquals(player, null))
            {
                player.IsReadyToPlay = e.Value2;
            }

            ApplicationServices.Trace.TraceMethodLeaving();
        }

        private void Model_OnResponseLobbyPlayerJoined(object sender, EventArgs<Player> e)
        {
            ApplicationServices.Trace.TraceMethodEntering();

            this._players.Add(e.Value);

            ApplicationServices.Trace.TraceMethodLeaving();
        }

        private void Model_OnResponseLobbyPlayerLeaved(object sender, EventArgs<int> e)
        {
            ApplicationServices.Trace.TraceMethodEntering();

            Player player = this._players.Single(p => p.Id.Equals(e.Value));
            this._players.Remove(player);

            ApplicationServices.Trace.TraceMethodLeaving();
        }

        private void Model_OnResponseLobbyPlayers(object sender, EventArgs<IEnumerable<Player>> e)
        {
            ApplicationServices.Trace.TraceMethodEntering();

            WeakEventManager<ILobbyModel, EventArgs<IEnumerable<Player>>>.RemoveHandler(this.Model, "OnResponseLobbyPlayers", this.Model_OnResponseLobbyPlayers);

            this._players = new ObservableCollection<Player>(e.Value);
            base.EndWaitingAction();

            ApplicationServices.Trace.TraceMethodLeaving();
        }

        private void Model_OnResponseLobbyTargetRoundsChanged(object sender, EventArgs<int> e)
        {
            ApplicationServices.Trace.TraceMethodEntering();

            this.Infos.TargetRounds = e.Value;

            ApplicationServices.Trace.TraceMethodLeaving();
        }

        private void Model_OnResponseLobbyTargetScoreChanged(object sender, EventArgs<int> e)
        {
            ApplicationServices.Trace.TraceMethodEntering();

            this.Infos.TargetScore = e.Value;

            ApplicationServices.Trace.TraceMethodLeaving();
        }
        #endregion

        #region Methods
        private bool CanReadyToPlay()
        {
            ApplicationServices.Trace.TraceMethodEntering();

            bool returnValue = !base.IsBusy
                && this._players != null;

            ApplicationServices.Trace.TraceReturnValue(returnValue); 
            ApplicationServices.Trace.TraceMethodEntering();
            return returnValue;
        }
        private void ReadyToPlay()
        {
            ApplicationServices.Trace.TraceMethodEntering();

            base.Model.RequestLobbyIsReadyToPlay(this.IsReady);

            ApplicationServices.Trace.TraceMethodEntering();
        }

        private bool CanGoBack()
        {
            ApplicationServices.Trace.TraceMethodEntering();

            Dictionary<string, object> arguments = new Dictionary<string, object>()
            {
                { "IsBusy", base.IsBusy }
            };

            bool returnValue = (bool)WorkflowInvoker.Invoke(new NotBusyActivity(), arguments)["ReturnValue"];

            ApplicationServices.Trace.TraceReturnValue(returnValue); 
            ApplicationServices.Trace.TraceMethodEntering();
            return returnValue;
        }
        private void GoBack()
        {
            ApplicationServices.Trace.TraceMethodEntering();

            if (base.Player.IsLobbyMaster)
            {
                base.Model.RequestLobbyClose();
            }
            else
            {
                base.Model.RequestLobbyLeave();
            }

            ApplicationServices.ViewWorkflow.SendMessage(Messages.LobbyGoback);

            ApplicationServices.Trace.TraceMethodEntering();
        }

        private bool CanStart()
        {
            ApplicationServices.Trace.TraceMethodEntering();

            bool returnValue = !IsBusy
                && !this.Infos.HasErrors
                && this.Player.IsLobbyMaster
                && this._players != null
                && this._players.All(p => p.IsReadyToPlay);

            ApplicationServices.Trace.TraceReturnValue(returnValue); 
            ApplicationServices.Trace.TraceMethodEntering();
            return returnValue;
        }
        private void Start()
        {
            ApplicationServices.Trace.TraceMethodEntering();

            base.Model.RequestLobbyLoadGame();
            ApplicationServices.ViewWorkflow.SendMessage(Messages.LobbyStart);

            ApplicationServices.Trace.TraceMethodEntering();
        }

        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate")]
        private static string GetLocalIP()
        {
            System.Diagnostics.TraceSource traceSource = System.ApplicationModel.Application.Current.ApplicationLifetimeObjects.OfType<Anagramme.Services.ITraceService>().Single().Source;
            traceSource.TraceMethodEntering();

            string localIP = string.Empty;
            IPHostEntry host = Dns.GetHostEntry(Dns.GetHostName());
            foreach (IPAddress ip in host.AddressList)
            {
                if (ip.AddressFamily == AddressFamily.InterNetwork)
                {
                    localIP = ip.ToString();
                }
            }

            traceSource.TraceMethodLeaving();
            return localIP;
        }

        private void GetInternetIPAsync()
        {
            ApplicationServices.Trace.TraceMethodEntering();

            WebClient webClient = null;
            DownloadStringCompletedEventHandler handler;

            handler = (s, e) =>
                {
                    if (object.ReferenceEquals(e.Error, null))
                    {
                        this.ServerIP += string.Concat(" | ", e.Result);
                        webClient.Dispose();
                    }
                };

            webClient = new WebClient();
            webClient.DownloadStringCompleted += handler;
            webClient.DownloadStringAsync(new Uri("http://myip.ozymo.com/"));

            ApplicationServices.Trace.TraceMethodLeaving();
        }
        #endregion

        #region Event Handlers
        private void Infos_GameModePropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
        {
            ApplicationServices.Trace.TraceMethodEntering();

            if (string.IsNullOrEmpty(this.Infos["GameMode"]))
            {
                base.Model.RequestLobbyChangeGameMode(this.Infos.GameMode);
            }

            ApplicationServices.Trace.TraceMethodLeaving();
        }

        private void Infos_TargetRoundsPropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
        {
            ApplicationServices.Trace.TraceMethodEntering();

            if (string.IsNullOrEmpty(this.Infos["TargetRounds"]))
            {
                base.Model.RequestLobbyChangeTargetRounds(this.Infos.TargetRounds);
            }

            ApplicationServices.Trace.TraceMethodLeaving();
        }

        private void Infos_TargetScorePropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
        {
            ApplicationServices.Trace.TraceMethodEntering();

            if (string.IsNullOrEmpty(this.Infos["TargetScore"]))
            {
                base.Model.RequestLobbyChangeTargetScore(this.Infos.TargetScore);
            }

            ApplicationServices.Trace.TraceMethodLeaving();
        }

        private void Infos_MinWordsLengthPropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
        {
            ApplicationServices.Trace.TraceMethodEntering();

            if (string.IsNullOrEmpty(this.Infos["MinWordsLength"]))
            {
                base.Model.RequestLobbyChangeMinWordsLength(this.Infos.MinWordsLength);
            }

            ApplicationServices.Trace.TraceMethodLeaving();
        }

        private void Infos_MaxWordsLengthPropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
        {
            ApplicationServices.Trace.TraceMethodEntering();

            if (string.IsNullOrEmpty(this.Infos["MaxWordsLength"]))
            {
                base.Model.RequestLobbyChangeMaxWordsLength(this.Infos.MaxWordsLength);
            }

            ApplicationServices.Trace.TraceMethodLeaving();
        }
        #endregion

        #region ObservableObject Overloads
        /// <summary>
        /// Soulève l'évènement <see cref="E:PropertyChanging"/>.
        /// </summary>
        /// <param name="e">Données de l'évènement.</param>
        protected override void OnPropertyChanging(System.ComponentModel.PropertyChangingEventArgs e)
        {
            if (object.ReferenceEquals(e, null))
            {
                throw new ArgumentNullException("e");
            }

            base.OnPropertyChanging(e);
            if (e.PropertyName.Equals("Infos"))
            {
                PropertyChangedEventManager.RemoveHandler(this.Infos, this.Infos_GameModePropertyChanged, "GameMode");
                PropertyChangedEventManager.RemoveHandler(this.Infos, this.Infos_TargetRoundsPropertyChanged, "TargetRounds");
                PropertyChangedEventManager.RemoveHandler(this.Infos, this.Infos_TargetScorePropertyChanged, "TargetScore");
                PropertyChangedEventManager.RemoveHandler(this.Infos, this.Infos_MinWordsLengthPropertyChanged, "MinWordsLength");
                PropertyChangedEventManager.RemoveHandler(this.Infos, this.Infos_MaxWordsLengthPropertyChanged, "MaxWordsLength");
            }
        }

        /// <summary>
        /// Soulève l'évènement <see cref="E:PropertyChanged"/>.
        /// </summary>
        /// <param name="e">Données de l'évènement.</param>
        protected override void OnPropertyChanged(System.ComponentModel.PropertyChangedEventArgs e)
        {
            if (object.ReferenceEquals(e, null))
            {
                throw new ArgumentNullException("e");
            }

            base.OnPropertyChanged(e);
            if (e.PropertyName.Equals("Infos"))
            {
                PropertyChangedEventManager.AddHandler(this.Infos, this.Infos_GameModePropertyChanged, "GameMode");
                PropertyChangedEventManager.AddHandler(this.Infos, this.Infos_TargetRoundsPropertyChanged, "TargetRounds");
                PropertyChangedEventManager.AddHandler(this.Infos, this.Infos_TargetScorePropertyChanged, "TargetScore");
                PropertyChangedEventManager.AddHandler(this.Infos, this.Infos_MinWordsLengthPropertyChanged, "MinWordsLength");
                PropertyChangedEventManager.AddHandler(this.Infos, this.Infos_MaxWordsLengthPropertyChanged, "MaxWordsLength");
            }
            if (e.PropertyName.Equals("IsReady"))
            {
                int playerId = base.Player.Id;
                Player player = this._players.SingleOrDefault(p => p.Id.Equals(playerId));
                if (object.ReferenceEquals(player, null))
                {
                    player.IsReadyToPlay = this.IsReady;
                }
            }
        }
        #endregion
    }
}
